/**
 * A modern, instance-based, and thread-safe configuration management module.
 *
 * This module leverages `dxEventBus` for thread-safe file operations. It provides a robust
 * way to manage one or more configuration files, each represented by an object instance,
 * ensuring a clean, flag-free API.
 *
 * ---
 * @example
 * // --- In the main thread (e.g., app.js at startup) ---
 * import dxConfiguration from '../dxmodules/dxConfiguration.js';
 *
 * // Define default values and initialize your configuration files.
 * // The first call to init() will automatically create and manage a dedicated worker thread
 * // for all subsequent configuration instances, ensuring thread-safe file operations.
 * const systemDefaults = { version: '1.0', mode: 'production' };
 * const sysConfig = dxConfiguration.init('/app/data/system.json', systemDefaults);
 *
 * // --- In any other thread or module ---
 * import dxConfiguration from '../dxmodules/dxConfiguration.js';
 *
 * // Get a handle to the already-initialized configuration instance
 * const sysConfig = dxConfiguration.getInstance('/app/data/system.json');
 *
 * if (sysConfig) {
 *     // Set a value and save it immediately (autosave = true).
 *     sysConfig.set('mode', 'development', true);
 *
 *     // Or, perform multiple changes and save them all at once.
 *     sysConfig.set('user', 'guest');
 *     sysConfig.set('theme', 'dark');
 *     sysConfig.save();
 * }
 * ---
 */
import dxMap from './dxMap.js';
import std from './dxStd.js';
import dxEventBus from './dxEventBus.js';

// Global dxMap instance for cross-thread state
const map = dxMap.get("default");

// Module-level cache for instances within the current thread to improve performance.
const instances = {};

// The main object to be exported
const dxConfiguration = {};

/**
 * Creates and initializes a configuration instance. This should be called once per configuration file,
 * typically from the main thread during application startup.
 * @param {string} savePath - The absolute path where the configuration file will be stored. This path acts as the unique identifier for the configuration.
 * @param {object} [defaultValues={}] - An object containing default key-value pairs. These are only used if the configuration file does not already exist.
 * @returns {object} The configuration instance.
 */
dxConfiguration.init = function (savePath, defaultValues = {}) {
    if (!savePath || typeof savePath !== 'string') {
        throw new Error('A valid savePath (string) is required.');
    }

    // One-time worker initialization, handled by the module itself.
    // This uses the cross-thread dxMap to ensure it only runs once per application lifetime.
    if (!map.get('__config_worker_initialized')) {
        map.put('__config_worker_initialized', true);
        dxEventBus.newWorker('config_worker', '/app/code/dxmodules/configurationWorker.js');
    }

    const prefix = `___config_${savePath}__`;
    const initLockKey = `${prefix}_init_lock`;

    // Thread-safe check using dxMap to ensure the core initialization logic runs only once.
    if (!map.get(initLockKey)) {
        map.put(initLockKey, 'initializing');

        let content;
        if (std.exist(savePath)) {
            try {
                content = std.parseExtJSON(std.loadFile(savePath));
            } catch (e) {
                std.err.puts(`[dxConfiguration] Failed to load or parse config file at '${savePath}'. Error: ${e.message || e}. Falling back to defaults.\n`);
                std.err.flush();
                content = defaultValues;
                // Overwrite the corrupted file with the default values for self-healing.
                std.saveFile(savePath, JSON.stringify(content, null, 2));
            }
        } else {
            content = defaultValues;
            // The initial save is synchronous to ensure the file is present before any thread tries to use it.
            std.saveFile(savePath, JSON.stringify(content, null, 2));
        }

        // Populate the shared dxMap with the initial configuration
        for (const [key, value] of Object.entries(content)) {
            map.put(prefix + key, value);
        }

        map.put(initLockKey, 'done');
    } else {
        // This is not an error, just a debug-level log to inform the developer.
        std.out.puts(`[dxConfiguration] Configuration for '${savePath}' has already been initialized. Skipping init.\n`);
        std.out.flush();
    }

    return dxConfiguration.getInstance(savePath);
};

/**
 * Retrieves an already initialized configuration instance. This is the standard way to access a configuration
 * from any thread after it has been initialized in the main thread.
 * @param {string} savePath - The absolute path of the configuration file.
 * @returns {object|null} The configuration instance, or null if it has not been successfully initialized yet.
 */
dxConfiguration.getInstance = function (savePath) {
    if (!savePath) {
        throw new Error('A valid savePath is required to get an instance.');
    }

    if (instances[savePath]) {
        return instances[savePath];
    }

    const prefix = `___config_${savePath}__`;
    const initLockKey = `${prefix}_init_lock`;

    if (map.get(initLockKey) !== 'done') {
        // This indicates init() has not been called or completed for this savePath.
        // It's crucial for developers to ensure init() is called in the main thread at startup.
        return null;
    }

    const instance = {
        _path: savePath,
        _prefix: prefix,

        /**
         * Retrieves a value by key from the configuration instance's memory.
         * @param {string} key - The key of the value to retrieve.
         * @returns {*} The value associated with the key, or `undefined` if the key does not exist.
         */
        get(key) {
            return map.get(this._prefix + key);
        },

        /**
         * Retrieves a shallow copy of all key-value pairs for this configuration instance.
         * @returns {object} An object containing all configuration data.
         */
        getAll() {
            const configData = {};
            const keys = map.keys().filter(k => k.startsWith(this._prefix) && !k.endsWith('_init_lock'));
            keys.forEach(k => {
                const key = k.substring(this._prefix.length);
                configData[key] = map.get(k);
            });
            return configData;
        },

        /**
         * Sets a key-value pair in the configuration instance's memory.
         * If the value is `null` or `undefined`, the key will be removed.
         * @param {string} key - The configuration key. Must be a non-empty string.
         * @param {*} value - The value to set. If `null` or `undefined`, the key will be deleted.
         * @param {boolean} [autosave=false] - If true, the configuration file will be saved to disk immediately after this operation.
         */
        set(key, value, autosave = false) {
            if (typeof key !== 'string' || !key.trim()) {
                throw new Error('Key must be a non-empty string.');
            }

            // If value is null or undefined, it's treated as a signal to delete the key.
            if (value === null || value === undefined) {
                map.del(this._prefix + key);
            } else {
                map.put(this._prefix + key, value);
            }

            if (autosave) {
                this.save();
            }
        },

        /**
         * Asynchronously saves the current in-memory configuration to the file on disk.
         * This is done by firing an event for the dedicated worker to handle.
         */
        save() {
            const content = this.getAll();
            // Fire an event for the worker to handle the file saving.
            dxEventBus.fire('__config_save', {
                path: this._path,
                content: content,
            });
        },

        /**
         * Removes a key from the configuration instance's memory.
         * @param {string} key - The configuration key to remove. Must be a non-empty string.
         * @param {boolean} [autosave=false] - If true, the configuration file will be saved to disk immediately after this operation.
         */
        clear(key, autosave = false) {
            if (typeof key !== 'string' || !key.trim()) {
                throw new Error('Key must be a non-empty string.');
            }
            map.del(this._prefix + key);
            if (autosave) {
                this.save();
            }
        },

        /**
         * Removes all key-value pairs associated with this configuration instance from memory
         * and fires an event to delete the corresponding configuration file from disk.
         */
        clearAll() {
            const keys = map.keys().filter(k => k.startsWith(this._prefix));
            keys.forEach(k => map.del(k));

            // Also remove the instance from the current thread's cache to complete the reset.
            delete instances[this._path];

            // Fire an event for the worker to handle the file deletion.
            dxEventBus.fire('__config_clear', {
                path: this._path,
            });
        }
    };

    instances[savePath] = instance;
    return instance;
};

export default dxConfiguration;